<!DOCTYPE html>
<html lang="en">
  <!-- https://www.cnblogs.com/ruizer/p/14134162.html -->

  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>拖拽表格</title>
    <script src="./mock.js"></script>
    <style>
      .a-center {
        text-align: center;
      }

      .a-right {
        text-align: right;
      }

      table,
      thead,
      tbody,
      td,
      th {
        border: 1px solid #ddd;
        border-collapse: collapse;
      }

      thead {
        position: -webkit-sticky;
        position: sticky;
        top: 0;
        border: 1px solid #ddd;
        z-index: 1;
      }

      th {
        background-color: #f7f7f7;
      }

      a {
        cursor: pointer;
        color: blue;
      }

      a.link {
        margin: 0 2px;
      }

      a.drag {
        cursor: move;
      }

      tr {
        height: 45px;
      }

      #app {
        width: 100%;
      }

      .body {
        position: relative;
        height: 600px;
        overflow: auto;
      }
    </style>
  </head>

  <body>
    <div id="app">
      <button onclick="addRow(1)">添加行</button>
      <button onclick="addCol(0)">添加列</button>
      <div class="body">
        <table id="table">
          <thead></thead>
          <tbody></tbody>
        </table>
      </div>
    </div>

    <script type="module">
      /*
      1. 数据绑定
      2. 表格绘制。
      3. 添加行
      4. 添加列/删除列
      5. 显示/隐藏列
      6. 列宽调整
      8. 树形数据
      9. 展开/收缩树
      10. 拖拽行
      */
      import { h, patch,u, Qinput, useObserver } from "./h.js";
      let state = {
        columns: [
          { type: "seq", width: 70, title: "Seq" },
          { type: "drag", width: 70, title: "drag" },
          { type: "operate", width: 260, title: "operate" },
          { field: "id", title: "Id" },
          { field: "name", title: "Name", width: 2000, edit: true },
          { field: "sex", type: "input", title: "Sex" },
          { field: "age", title: "Age" },
        ],
      };
      let txt = h(123);
      let alink = h("a", "123");
      console.warn("h123", txt, typeof txt);
      console.warn("alink", alink, typeof alink);
      const observer = useObserver();
      let list = Mock.mock({
        "data|5": [
          {
            "id|+1": 1,
            name: "@cname",
            "sex|1": ["男", "女"],
            "age|1-100": 1,
            address: "@city(true)",
          },
        ],
      });
      console.warn("list", list);
      const active = {
        td: null,
        row: null,
        drag: null,
        clearActive: (name) => {
          console.warn("docment", name, active.td);
          if (active.td) {
            active.td._edit = false;
            active.td._render();
            active.td = null;
          }
        },
      };

      let dom = {
        init(columns) {
          const thead = table.querySelector("thead");
          let ths = [];
          let cols = [];
          let colTh = {
            target: null,
            x: 0,
            w: 0,
          }; // 拖拽的列
          columns.forEach((c, i) => {
            let th = h(
              "th",
              {
                onMousedown: (e) => {
                  if (!(e && e.target)) return;
                  const vm = e.target;
                  // 更改鼠标样式
                  if (e.offsetX > vm.offsetWidth - 10) {
                    colTh.target = e.target;
                    colTh.x = e.x;
                    colTh.w = vm.offsetWidth;
                  }
                },
                onMousemove: (e) => {
                  if (!(e && e.target)) return;
                  const vm = e.target;
                  // 更改鼠标样式
                  if (e.offsetX > vm.offsetWidth - 10) {
                    vm.style.cursor = "col-resize";
                  } else {
                    vm.style.cursor = "default";
                  }
                  if (colTh.target !== null && colTh.target === vm) {
                    console.warn("move", e, colTh, vm);
                    let name = vm.getAttribute("name");
                    let width = colTh.w+colTh.x - e.clientX;
                    table.querySelector(`col[name="${name}"]`).style.width =
                      width + "px";
                  }
                },
                onMouseUp: (e) => {
                  colTh = { target: null, x: 0, w: 0 };
                },
                name: `c${i}`,
              },
              c.title
            );
            ths.push(th);
            let style = "";
            if (c.width) {
              style = ` style="width: ${c.width}px;"`;
            } else {
              style = ` style="width: 800px;"`;
            }
            let col = `<col name="c${i}"${style}>`;
            cols.push(col);
          });
          table.insertBefore(h("colgroup", {}, cols.join("")), thead);
          thead.insertBefore(h("tr", {}, ths), null);
        },
        rowOperate(tr, code) {
          console.warn("rowOperate", tr, code);
          let index = list.data.findIndex((s) => s == tr._q);
          if (code == "delete") {
            let row = list.data.splice(index, 1);
            tr.remove();
            observer.send("deleteRow", row, tr);
          } else {
            let row = Mock.mock({
              id: id++,
              name: "@cname",
              "sex|1": ["男", "女"],
              "age|1-100": 1,
              address: "@city(true)",
            });
            const tbody = table.querySelector("tbody");

            let trNew = dom.renderTr(
              state.columns,
              row,
              list.data.length - 1,
              null
            );
            trNew._q = row;
            if (code == "insert") {
              list.data.splice(index, 0, row);
              tbody.insertBefore(trNew, tr);
              observer.send("addRow", row, trNew);
            } else if (code == "add") {
              list.data.splice(index + 1, 0, row);
              tbody.insertBefore(trNew, tr.nextSibling);
              observer.send("addRow", row, trNew);
            }
            dom.resetSeq();
          }
        },
        bind(data, columns) {
          const { rowOperate, renderTr } = dom;
          const tbody = table.querySelector("tbody");
          const trs = [];
          data.forEach((d, j) => {
            tbody.append(renderTr(columns, d, j));
          });
        },
        renderBody(columns, rows, parent = null) {},
        renderTr(columns, row, j, parent = null) {
          let { renderTr, renderTd } = dom;
          let d = row;
          let tr = h("tr", {
            rid: d.id,
            draggable: true,
            onDragstart: (e) => {
              const data = e.dataTransfer;
              active.drag = e.target;
            },
            onDragover: (e) => e.preventDefault(),
            onDrop: (e) => {
              // tbody.insertBefore(drag, e.target.parentNode);
              let currentTr = e.target;
              console.warn("tr", currentTr);
              while (currentTr.tagName != "TR") {
                currentTr = currentTr.parentNode;
              }
              if (currentTr.tagName == "TR") {
                currentTr.before(active.drag);
                console.warn(currentTr._q, active.drag._q);
              }
              e.preventDefault();
              dom.resetSeq();
            },
          });
          tr._q = row;
          let tds = columns.map((c, i) => renderTd(c, d, j, tr));
          tr.append(...tds);
          return tr;
        },
        renderTd(column, row, rowIndex, parent = null) {
          let d = row;
          let c = column;
          let th = parent;
          //   let j = list;
          let td;
          let value = d[c.field];
          if (c.type == "seq") {
            value = rowIndex + 1;
          } else if (c.type == "drag") {
            value = `<a class="drag">X</a>`;
          } else if (c.type == "input") {
            value = h(
              Qinput,
              {
                vmodel: c.field,
              },
              d
            );
          } else if (c.type == "operate") {
            let a = h(
              "a",
              {
                className: "link",
                onClick: (e) => {
                  rowOperate(th, "insert");
                },
              },
              "^"
            );
            let b = h(
              "a",
              {
                className: "link",
                onClick: (e) => {
                  rowOperate(th, "add");
                },
              },
              "+"
            );
            let c = h(
              "a",
              {
                className: "link",
                onClick: (e) => {
                  rowOperate(th, "delete");
                },
              },
              "x"
            );
            value = [a, b, c];
          }
          let props = {
            className: "a-center",
          };
          if (column.edit) {
            let activeTd = (e) => {
              console.warn("td", active.td);
              if (!td._edit) {
                console.warn("d", d, d[c.field]);
                e.stopPropagation();
                td.innerHTML = "";
                let input = h(
                  Qinput,
                  {
                    vmodel: c.field,
                  },
                  d
                );
                td.append(input);
                input.focus();
                if (input.setSelectionRange) {
                  var len = d[c.field].length;
                  input.setSelectionRange(len, len);
                }
                td._edit = true;
                td._render = () => {
                  td.innerHTML = d[c.field];
                  td.addEventListener("click", activeTd, { once: true });
                };

                if (active.td != td) {
                  active.clearActive("td");
                  active.td = td;
                }
              } else {
                td._edit = false;
              }
            };

            props["onClick.once"] = activeTd;
          }
          td = h("td", props, value);

          return td;
        },
        resetSeq() {
         let seqs= table
            .querySelectorAll("tbody tr td:first-child")
            // console.warn(seqs);
            seqs.forEach((s, j) => {
            // s.innerText = j + 1;
              u(s,j+1)
            });
        },
      };
      let operate = {
        reload() {},
        insert() {},
        insertAt() {},
        get() {},
        remove() {},
        update() {},
        addColumn() {},
        removeColumn() {},
        hideColumn() {},
        showColumn() {},
      };
      const { rowOperate, renderTr } = dom;
      document.addEventListener("click", (e) => {
        if (active.td && !active.td.contains(e.target)) {
          active.clearActive("document");
        }
      });
      window.data = list.data;
      window.active = active;
      dom.init(state.columns);
      dom.bind(list.data, state.columns);
      let id = 20;
      window.addRow = (d) => {
        let row = Mock.mock({
          id: id++,
          name: "@cname",
          "sex|1": ["男", "女"],
          "age|1-100": 1,
          address: "@city(true)",
        });
        console.warn("row", row);
        list.data.push(row);
        const tbody = table.querySelector("tbody");
        let tr = dom.renderTr(state.columns, row, list.data.length - 1, null);
        patch(tbody, tr);
        observer.send("addRow", row, tr);
      };
      observer.listen("addRow", (row, tr) => {
        console.warn("listen:addRow", row, tr);
      });
      observer.listen("deleteRow", (row, tr) => {
        console.warn("listen:deleteRow", row, tr);
      });
      window.set = new WeakSet([...list.data]);
    </script>
  </body>
</html>
